/**
 * 
 */
package fr.ece.ing4.si.mc.MonteCarlo;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import fr.ece.ing4.si.mc.gui.ResultWindow;
import fr.ece.ing4.si.mc.multiThreading.MultiMonteCarloMethod;

/**
 * @author Cesar Berezowski, Gaspard Peyrot
 *
 */
public class MultiMonteCarloController extends MonteCarloAbstract implements Runnable {

	private int z;
	private double s;
	private double x;
	private double r;
	private double drift;
	private double dt;
	private double vSqrdt;
	private double t;
	private int nSteps;
	private int nSimulations;
	private int poolSize;
	private ResultWindow myView;
	private int counter;
	
	
	public MultiMonteCarloController() { }
	
	/**
	 * To build the controller
	 * @param callPutFlag flag for calling or putting
	 * @param s underlying price
	 * @param x strike price
	 * @param t time left to maturity
	 * @param r interest rate without risks
	 * @param b detention rate fees
	 * @param v volatility of the underlying price
	 * @param nSteps number of steps to do during calculations
	 * @param nSimulations number of simulations to do
	 * @param myView pointer on the result window from gui
	 */
	public void build(String callPutFlag, double s, double x, double r, double t, 
			double b, double v,int nSteps, int nSimulations, ResultWindow myView){

		if(callPutFlag == "c") z = 1;
		else if(callPutFlag == "p") z = -1;
		
		this.myView = myView;
		this.s = s;
		this.x = x;
		this.r = r;
		this.t = t;
		this.nSteps = nSteps;
		this.nSimulations = nSimulations;
		
		dt = t / nSteps;
		drift = ((b - Math.pow(v, 2)) / 2) * dt;
		vSqrdt = v * Math.sqrt(dt);
		
		int numberOfCores = Runtime.getRuntime().availableProcessors();
		double blockingCoefficient = 0.9;
		poolSize = (int)(numberOfCores / (1 - blockingCoefficient));
		
		
		System.out.println("Number of Cores available is " + numberOfCores);
		System.out.println("Pool size is " + poolSize);
		
		
	}

	@Override
	public void run() {
		
		this.calculate();
	}

	/**
	 * Launch computation
	 */
	@Override
	public void calculate() {
		
		long execTime = System.currentTimeMillis(); 
		
		final List<Callable<Double>> simulations = new ArrayList<Callable<Double>>();
		final ExecutorService executorPool;
		final List<Future<Double>> simulationResults;
		double sum = 0.0;
		int cmp = 0;
		
		for(int i = 0; i < nSimulations; i++){
			simulations.add(new MultiMonteCarloMethod(i, z, drift, vSqrdt, x, nSteps, s, this));
		}
		
		executorPool = Executors.newFixedThreadPool(poolSize);
		
		try {
			simulationResults = executorPool.invokeAll(simulations, 10000, TimeUnit.SECONDS);
			
			for(final Future<Double> oneResult : simulationResults){ 
				sum += oneResult.get(); 
				if(oneResult.get() == 0) cmp++;
			}
			
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		executorPool.shutdown();
		
		double netAssetValue = (Math.exp(-r* t) * (sum / nSimulations));
		System.out.println("Calculated price : " + netAssetValue);
		
		execTime = System.currentTimeMillis() - execTime;
		System.out.println("Execution time : " + execTime + "ms");
	
		System.out.println("Number of threads returning 0 : " + cmp);
		
		myView.setValueAndTime(netAssetValue, execTime);
	}

	/**
	 * Updates the view progression
	 */
	@Override
	public void updateProgress() {
		
		counter++;
		
		for(int k = 1; k < (nSimulations/100)+1; k++){
			if(counter == (int)(k*nSimulations)/100){
				myView.updateProgress(1);
				System.out.println(counter);
			}
		}
	}
}
